1   // Copyright 2006-2014 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   // http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  
15  package org.apache.tapestry5.ioc.internal.util;
16  
17  import org.apache.tapestry5.func.F;
18  import org.apache.tapestry5.func.Mapper;
19  import org.apache.tapestry5.func.Predicate;
20  import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
21  import org.apache.tapestry5.ioc.*;
22  import org.apache.tapestry5.ioc.annotations.*;
23  import org.apache.tapestry5.ioc.def.*;
24  import org.apache.tapestry5.ioc.internal.ServiceDefImpl;
25  import org.apache.tapestry5.ioc.services.Coercion;
26  import org.apache.tapestry5.ioc.util.ExceptionUtils;
27  import org.apache.tapestry5.plastic.PlasticUtils;
28  import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
29  import org.slf4j.Logger;
30  
31  import javax.annotation.PostConstruct;
32  import javax.inject.Named;
33  
34  import java.io.Closeable;
35  import java.io.IOException;
36  import java.lang.annotation.Annotation;
37  import java.lang.annotation.Retention;
38  import java.lang.annotation.RetentionPolicy;
39  import java.lang.reflect.*;
40  import java.net.URL;
41  import java.util.*;
42  import java.util.concurrent.atomic.AtomicLong;
43  import java.util.regex.Matcher;
44  import java.util.regex.Pattern;
45  
46  /**
47   * Utilities used within various internal implementations of the tapestry-ioc module.
48   */
49  @SuppressWarnings("all")
50  public class InternalUtils
51  {
52      /**
53       * @since 5.2.2
54       */
55      public static final boolean SERVICE_CLASS_RELOADING_ENABLED = Boolean.parseBoolean(System.getProperty(
56              IOCConstants.SERVICE_CLASS_RELOADING_ENABLED, "true"));
57  
58      /**
59       * Converts a method to a user presentable string using a {@link PlasticProxyFactory} to obtain a {@link Location}
60       * (where possible). {@link #asString(Method)} is used under the covers, to present a detailed, but not excessive,
61       * description of the class, method and parameters.
62       *
63       * @param method
64       *         method to convert to a string
65       * @param proxyFactory
66       *         used to obtain the {@link Location}
67       * @return the method formatted for presentation to the user
68       */
69      public static String asString(Method method, PlasticProxyFactory proxyFactory)
70      {
71          Location location = proxyFactory.getMethodLocation(method);
72  
73          return location != null ? location.toString() : asString(method);
74      }
75  
76      /**
77       * Converts a method to a user presentable string consisting of the containing class name, the method name, and the
78       * short form of the parameter list (the class name of each parameter type, shorn of the package name portion).
79       *
80       * @param method
81       * @return short string representation
82       */
83      public static String asString(Method method)
84      {
85          return InternalCommonsUtils.asString(method);
86      }
87  
88      /**
89       * Returns the size of an object array, or null if the array is empty.
90       */
91  
92      public static int size(Object[] array)
93      {
94          return array == null ? 0 : array.length;
95      }
96  
97      public static int size(Collection collection)
98      {
99          return collection == null ? 0 : collection.size();
100     }
101 
102     /**
103      * Strips leading "_" and "$" and trailing "_" from the name.
104      */
105     public static String stripMemberName(String memberName)
106     {
107         return InternalCommonsUtils.stripMemberName(memberName);
108     }
109 
110     /**
111      * Converts an enumeration (of Strings) into a sorted list of Strings.
112      */
113     public static List<String> toList(Enumeration e)
114     {
115         List<String> result = CollectionFactory.newList();
116 
117         while (e.hasMoreElements())
118         {
119             String name = (String) e.nextElement();
120 
121             result.add(name);
122         }
123 
124         Collections.sort(result);
125 
126         return result;
127     }
128 
129     /**
130      * Finds a specific annotation type within an array of annotations.
131      *
132      * @param <T>
133      * @param annotations
134      *         to search
135      * @param annotationClass
136      *         to match
137      * @return the annotation instance, if found, or null otherwise
138      */
139     public static <T extends Annotation> T findAnnotation(Annotation[] annotations, Class<T> annotationClass)
140     {
141         for (Annotation a : annotations)
142         {
143             if (annotationClass.isInstance(a))
144                 return annotationClass.cast(a);
145         }
146 
147         return null;
148     }
149 
150     private static ObjectCreator<Object> asObjectCreator(final Object fixedValue)
151     {
152         return new ObjectCreator<Object>()
153         {
154             @Override
155             public Object createObject()
156             {
157                 return fixedValue;
158             }
159         };
160     }
161 
162     private static ObjectCreator calculateInjection(final Class injectionType, Type genericType, final Annotation[] annotations,
163                                                     final ObjectLocator locator, InjectionResources resources)
164     {
165         final AnnotationProvider provider = new AnnotationProvider()
166         {
167             @Override
168             public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
169             {
170                 return findAnnotation(annotations, annotationClass);
171             }
172         };
173 
174         // At some point, it would be nice to eliminate InjectService, and rely
175         // entirely on service interface type and point-of-injection markers.
176 
177         InjectService is = provider.getAnnotation(InjectService.class);
178 
179         if (is != null)
180         {
181             String serviceId = is.value();
182 
183             return asObjectCreator(locator.getService(serviceId, injectionType));
184         }
185 
186         Named named = provider.getAnnotation(Named.class);
187 
188         if (named != null)
189         {
190             return asObjectCreator(locator.getService(named.value(), injectionType));
191         }
192 
193         // In the absence of @InjectService, try some autowiring. First, does the
194         // parameter type match one of the resources (the parameter defaults)?
195 
196         if (provider.getAnnotation(Inject.class) == null)
197         {
198             Object result = resources.findResource(injectionType, genericType);
199 
200             if (result != null)
201             {
202                 return asObjectCreator(result);
203             }
204         }
205 
206         // TAP5-1765: For @Autobuild, special case where we always compute a fresh value
207         // for the injection on every use.  Elsewhere, we compute once when generating the
208         // construction plan and just use the singleton value repeatedly.
209 
210         if (provider.getAnnotation(Autobuild.class) != null)
211         {
212             return new ObjectCreator()
213             {
214                 @Override
215                 public Object createObject()
216                 {
217                     return locator.getObject(injectionType, provider);
218                 }
219             };
220         }
221 
222         // Otherwise, make use of the MasterObjectProvider service to resolve this type (plus
223         // any other information gleaned from additional annotation) into the correct object.
224 
225         return asObjectCreator(locator.getObject(injectionType, provider));
226     }
227 
228     public static ObjectCreator[] calculateParametersForMethod(Method method, ObjectLocator locator,
229                                                                InjectionResources resources, OperationTracker tracker)
230     {
231 
232         return calculateParameters(locator, resources, method.getParameterTypes(), method.getGenericParameterTypes(),
233                 method.getParameterAnnotations(), tracker);
234     }
235 
236     public static ObjectCreator[] calculateParameters(final ObjectLocator locator, final InjectionResources resources,
237                                                       Class[] parameterTypes, final Type[] genericTypes, Annotation[][] parameterAnnotations,
238                                                       OperationTracker tracker)
239     {
240         int parameterCount = parameterTypes.length;
241 
242         ObjectCreator[] parameters = new ObjectCreator[parameterCount];
243 
244         for (int i = 0; i < parameterCount; i++)
245         {
246             final Class type = parameterTypes[i];
247             final Type genericType = genericTypes[i];
248             final Annotation[] annotations = parameterAnnotations[i];
249 
250             String description = String.format("Determining injection value for parameter #%d (%s)", i + 1,
251                     PlasticUtils.toTypeName(type));
252 
253             final Invokable<ObjectCreator> operation = new Invokable<ObjectCreator>()
254             {
255                 @Override
256                 public ObjectCreator invoke()
257                 {
258                     return calculateInjection(type, genericType, annotations, locator, resources);
259                 }
260             };
261 
262             parameters[i] = tracker.invoke(description, operation);
263         }
264 
265         return parameters;
266     }
267 
268     /**
269      * Injects into the fields (of all visibilities) when the {@link org.apache.tapestry5.ioc.annotations.Inject} or
270      * {@link org.apache.tapestry5.ioc.annotations.InjectService} annotations are present.
271      *
272      * @param object
273      *         to be initialized
274      * @param locator
275      *         used to resolve external dependencies
276      * @param resources
277      *         provides injection resources for fields
278      * @param tracker
279      *         track operations
280      */
281     public static void injectIntoFields(final Object object, final ObjectLocator locator,
282                                         final InjectionResources resources, OperationTracker tracker)
283     {
284         Class clazz = object.getClass();
285 
286         while (clazz != Object.class)
287         {
288             Field[] fields = clazz.getDeclaredFields();
289 
290             for (final Field f : fields)
291             {
292                 // Ignore all static and final fields.
293 
294                 int fieldModifiers = f.getModifiers();
295 
296                 if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers))
297                     continue;
298 
299                 final AnnotationProvider ap = new AnnotationProvider()
300                 {
301                     @Override
302                     public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
303                     {
304                         return f.getAnnotation(annotationClass);
305                     }
306                 };
307 
308                 String description = String.format("Calculating possible injection value for field %s.%s (%s)",
309                         clazz.getName(), f.getName(),
310                         PlasticUtils.toTypeName(f.getType()));
311 
312                 tracker.run(description, new Runnable()
313                 {
314                     @Override
315                     public void run()
316                     {
317                         final Class<?> fieldType = f.getType();
318 
319                         InjectService is = ap.getAnnotation(InjectService.class);
320                         if (is != null)
321                         {
322                             inject(object, f, locator.getService(is.value(), fieldType));
323                             return;
324                         }
325 
326                         if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null)
327                         {
328                             Object value = resources.findResource(fieldType, f.getGenericType());
329 
330                             if (value != null)
331                             {
332                                 inject(object, f, value);
333                                 return;
334                             }
335 
336                             inject(object, f, locator.getObject(fieldType, ap));
337                             return;
338                         }
339 
340                         if (ap.getAnnotation(javax.inject.Inject.class) != null)
341                         {
342                             Named named = ap.getAnnotation(Named.class);
343 
344                             if (named == null)
345                             {
346                                 Object value = resources.findResource(fieldType, f.getGenericType());
347 
348                                 if (value != null)
349                                 {
350                                     inject(object, f, value);
351                                     return;
352                                 }
353 
354                                 inject(object, f, locator.getObject(fieldType, ap));
355                             } else
356                             {
357                                 inject(object, f, locator.getService(named.value(), fieldType));
358                             }
359 
360                             return;
361                         }
362 
363                         // Ignore fields that do not have the necessary annotation.
364 
365                     }
366                 });
367             }
368 
369             clazz = clazz.getSuperclass();
370         }
371     }
372 
373     private synchronized static void inject(Object target, Field field, Object value)
374     {
375         try
376         {
377             if (!field.isAccessible())
378                 field.setAccessible(true);
379 
380             field.set(target, value);
381 
382             // Is there a need to setAccessible back to false?
383         } catch (Exception ex)
384         {
385             throw new RuntimeException(String.format("Unable to set field '%s' of %s to %s: %s", field.getName(),
386                     target, value, ExceptionUtils.toMessage(ex)));
387         }
388     }
389 
390     /**
391      * Joins together some number of elements to form a comma separated list.
392      */
393     public static String join(List elements)
394     {
395         return InternalCommonsUtils.join(elements);
396     }
397 
398     /**
399      * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the
400      * string "(blank)".
401      *
402      * @param elements
403      *         objects to be joined together
404      * @param separator
405      *         used between elements when joining
406      */
407     public static String join(List elements, String separator)
408     {
409         return InternalCommonsUtils.join(elements, separator);
410     }
411 
412     /**
413      * Creates a sorted copy of the provided elements, then turns that into a comma separated list.
414      *
415      * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or
416      *         empty
417      */
418     public static String joinSorted(Collection elements)
419     {
420         return InternalCommonsUtils.joinSorted(elements);
421     }
422 
423     /**
424      * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace).
425      */
426 
427     public static boolean isBlank(String input)
428     {
429         return InternalCommonsUtils.isBlank(input);
430     }
431 
432     /**
433      * Returns true if the input is an empty collection.
434      */
435 
436     public static boolean isEmptyCollection(Object input)
437     {
438         if (input instanceof Collection)
439         {
440             return ((Collection) input).isEmpty();
441         }
442 
443         return false;
444     }
445 
446     public static boolean isNonBlank(String input)
447     {
448         return InternalCommonsUtils.isNonBlank(input);
449     }
450 
451     /**
452      * Capitalizes a string, converting the first character to uppercase.
453      */
454     public static String capitalize(String input)
455     {
456         return InternalCommonsUtils.capitalize(input);
457     }
458 
459     /**
460      * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not
461      * convertable to a location.
462      */
463     
464     public static Location locationOf(Object location)
465     {
466         return InternalCommonsUtils.locationOf(location);
467     }
468 
469     public static <K, V> Set<K> keys(Map<K, V> map)
470     {
471         if (map == null)
472             return Collections.emptySet();
473 
474         return map.keySet();
475     }
476 
477     /**
478      * Gets a value from a map (which may be null).
479      *
480      * @param <K>
481      * @param <V>
482      * @param map
483      *         the map to extract from (may be null)
484      * @param key
485      * @return the value from the map, or null if the map is null
486      */
487 
488     public static <K, V> V get(Map<K, V> map, K key)
489     {
490         if (map == null)
491             return null;
492 
493         return map.get(key);
494     }
495 
496     /**
497      * Returns true if the method provided is a static method.
498      */
499     public static boolean isStatic(Method method)
500     {
501         return Modifier.isStatic(method.getModifiers());
502     }
503 
504     public static <T> Iterator<T> reverseIterator(final List<T> list)
505     {
506         final ListIterator<T> normal = list.listIterator(list.size());
507 
508         return new Iterator<T>()
509         {
510             @Override
511             public boolean hasNext()
512             {
513                 return normal.hasPrevious();
514             }
515 
516             @Override
517             public T next()
518             {
519                 return normal.previous();
520             }
521 
522             @Override
523             public void remove()
524             {
525                 throw new UnsupportedOperationException();
526             }
527         };
528     }
529 
530     /**
531      * Return true if the input string contains the marker for symbols that must be expanded.
532      */
533     public static boolean containsSymbols(String input)
534     {
535         return InternalCommonsUtils.containsSymbols(input);
536     }
537 
538     /**
539      * Searches the string for the final period ('.') character and returns everything after that. The input string is
540      * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property
541      * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period
542      * character.
543      */
544     public static String lastTerm(String input)
545     {
546         return InternalCommonsUtils.lastTerm(input);
547     }
548 
549     /**
550      * Searches a class for the "best" constructor, the public constructor with the most parameters. Returns null if
551      * there are no public constructors. If there is more than one constructor with the maximum number of parameters, it
552      * is not determined which will be returned (don't build a class like that!). In addition, if a constructor is
553      * annotated with {@link org.apache.tapestry5.ioc.annotations.Inject}, it will be used (no check for multiple such
554      * constructors is made, only at most a single constructor should have the annotation).
555      *
556      * @param clazz
557      *         to search for a constructor for
558      * @return the constructor to be used to instantiate the class, or null if no appropriate constructor was found
559      */
560     public static Constructor findAutobuildConstructor(Class clazz)
561     {
562         Constructor[] constructors = clazz.getConstructors();
563 
564         switch (constructors.length)
565         {
566             case 1:
567 
568                 return constructors[0];
569 
570             case 0:
571 
572                 return null;
573 
574             default:
575                 break;
576         }
577 
578         Constructor standardConstructor = findConstructorByAnnotation(constructors, Inject.class);
579         Constructor javaxConstructor = findConstructorByAnnotation(constructors, javax.inject.Inject.class);
580 
581         if (standardConstructor != null && javaxConstructor != null)
582             throw new IllegalArgumentException(
583                     String.format(
584                             "Too many autobuild constructors found: use either @%s or @%s annotation to mark a single constructor for autobuilding.",
585                             Inject.class.getName(), javax.inject.Inject.class.getName()));
586 
587         if (standardConstructor != null)
588         {
589             return standardConstructor;
590         }
591 
592         if (javaxConstructor != null)
593         {
594             return javaxConstructor;
595         }
596 
597         // Choose a constructor with the most parameters.
598 
599         Comparator<Constructor> comparator = new Comparator<Constructor>()
600         {
601             @Override
602             public int compare(Constructor o1, Constructor o2)
603             {
604                 return o2.getParameterTypes().length - o1.getParameterTypes().length;
605             }
606         };
607 
608         Arrays.sort(constructors, comparator);
609 
610         return constructors[0];
611     }
612 
613     private static <T extends Annotation> Constructor findConstructorByAnnotation(Constructor[] constructors,
614                                                                                   Class<T> annotationClass)
615     {
616         for (Constructor c : constructors)
617         {
618             if (c.getAnnotation(annotationClass) != null)
619                 return c;
620         }
621 
622         return null;
623     }
624 
625     /**
626      * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map
627      * that allows multiple values for the same key.
628      *
629      * @param map
630      *         to store value into
631      * @param key
632      *         for which a value is added
633      * @param value
634      *         to add
635      * @param <K>
636      *         the type of key
637      * @param <V>
638      *         the type of the list
639      */
640     public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value)
641     {
642         InternalCommonsUtils.addToMapList(map, key, value);
643     }
644 
645     /**
646      * Validates that the marker annotation class had a retention policy of runtime.
647      *
648      * @param markerClass
649      *         the marker annotation class
650      */
651     public static void validateMarkerAnnotation(Class markerClass)
652     {
653         Retention policy = (Retention) markerClass.getAnnotation(Retention.class);
654 
655         if (policy != null && policy.value() == RetentionPolicy.RUNTIME)
656             return;
657 
658         throw new IllegalArgumentException(UtilMessages.badMarkerAnnotation(markerClass));
659     }
660 
661     public static void validateMarkerAnnotations(Class[] markerClasses)
662     {
663         for (Class markerClass : markerClasses)
664             validateMarkerAnnotation(markerClass);
665     }
666 
667     public static void close(Closeable stream)
668     {
669         if (stream != null)
670             try
671             {
672                 stream.close();
673             } catch (IOException ex)
674             {
675                 // Ignore.
676             }
677     }
678 
679     /**
680      * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name.
681      *
682      * @param exception
683      *         to extract message from
684      * @return message or class name
685      * @deprecated Deprecated in 5.4; use {@link ExceptionUtils#toMessage(Throwable)} instead.
686      */
687     // Cause it gets used a lot outside of Tapestry proper even though it is internal.
688     public static String toMessage(Throwable exception)
689     {
690         return ExceptionUtils.toMessage(exception);
691     }
692 
693     public static void validateConstructorForAutobuild(Constructor constructor)
694     {
695         Class clazz = constructor.getDeclaringClass();
696 
697         if (!Modifier.isPublic(clazz.getModifiers()))
698             throw new IllegalArgumentException(String.format(
699                     "Class %s is not a public class and may not be autobuilt.", clazz.getName()));
700 
701         if (!Modifier.isPublic(constructor.getModifiers()))
702             throw new IllegalArgumentException(
703                     String.format(
704                             "Constructor %s is not public and may not be used for autobuilding an instance of the class. "
705                                     + "You should make the constructor public, or mark an alternate public constructor with the @Inject annotation.",
706                             constructor));
707     }
708 
709     /**
710      * @since 5.3
711      */
712     public static final Mapper<Class, AnnotationProvider> CLASS_TO_AP_MAPPER = new Mapper<Class, AnnotationProvider>()
713     {
714         @Override
715         public AnnotationProvider map(final Class element)
716         {
717             return toAnnotationProvider(element);
718         }
719 
720     };
721 
722     /**
723      * @since 5.3
724      */
725     public static AnnotationProvider toAnnotationProvider(final Class element)
726     {
727         return InternalCommonsUtils.toAnnotationProvider(element);
728     }
729 
730     /**
731      * @since 5.3
732      */
733     public static final Mapper<Method, AnnotationProvider> METHOD_TO_AP_MAPPER = new Mapper<Method, AnnotationProvider>()
734     {
735         @Override
736         public AnnotationProvider map(final Method element)
737         {
738             return toAnnotationProvider(element);
739         }
740     };
741 
742     public static final Method findMethod(Class containingClass, String methodName, Class... parameterTypes)
743     {
744         if (containingClass == null)
745             return null;
746 
747         try
748         {
749             return containingClass.getMethod(methodName, parameterTypes);
750         } catch (SecurityException ex)
751         {
752             throw new RuntimeException(ex);
753         } catch (NoSuchMethodException ex)
754         {
755             return null;
756         }
757     }
758 
759     /**
760      * @since 5.3
761      */
762     public static ServiceDef3 toServiceDef3(ServiceDef sd)
763     {
764         if (sd instanceof ServiceDef3)
765             return (ServiceDef3) sd;
766 
767         final ServiceDef2 sd2 = toServiceDef2(sd);
768 
769         return new ServiceDef3()
770         {
771             // ServiceDef3 methods:
772 
773             @Override
774             public AnnotationProvider getClassAnnotationProvider()
775             {
776                 return toAnnotationProvider(getServiceInterface());
777             }
778 
779             @Override
780             public AnnotationProvider getMethodAnnotationProvider(final String methodName, final Class... argumentTypes)
781             {
782                 return toAnnotationProvider(findMethod(getServiceInterface(), methodName, argumentTypes));
783             }
784             
785             @Override
786             public Class getServiceImplementation() 
787             {
788                 return null;
789             }
790 
791             // ServiceDef2 methods:
792 
793             @Override
794             public boolean isPreventDecoration()
795             {
796                 return sd2.isPreventDecoration();
797             }
798 
799             @Override
800             public ObjectCreator createServiceCreator(ServiceBuilderResources resources)
801             {
802                 return sd2.createServiceCreator(resources);
803             }
804 
805             @Override
806             public String getServiceId()
807             {
808                 return sd2.getServiceId();
809             }
810 
811             @Override
812             public Set<Class> getMarkers()
813             {
814                 return sd2.getMarkers();
815             }
816 
817             @Override
818             public Class getServiceInterface()
819             {
820                 return sd2.getServiceInterface();
821             }
822 
823             @Override
824             public String getServiceScope()
825             {
826                 return sd2.getServiceScope();
827             }
828 
829             @Override
830             public boolean isEagerLoad()
831             {
832                 return sd2.isEagerLoad();
833             }
834 
835         };
836     }
837 
838     public static ServiceDef2 toServiceDef2(final ServiceDef sd)
839     {
840         if (sd instanceof ServiceDef2)
841             return (ServiceDef2) sd;
842 
843         return new ServiceDef2()
844         {
845             // ServiceDef2 methods:
846 
847             @Override
848             public boolean isPreventDecoration()
849             {
850                 return false;
851             }
852 
853             // ServiceDef methods:
854 
855             @Override
856             public ObjectCreator createServiceCreator(ServiceBuilderResources resources)
857             {
858                 return sd.createServiceCreator(resources);
859             }
860 
861             @Override
862             public String getServiceId()
863             {
864                 return sd.getServiceId();
865             }
866 
867             @Override
868             public Set<Class> getMarkers()
869             {
870                 return sd.getMarkers();
871             }
872 
873             @Override
874             public Class getServiceInterface()
875             {
876                 return sd.getServiceInterface();
877             }
878 
879             @Override
880             public String getServiceScope()
881             {
882                 return sd.getServiceScope();
883             }
884 
885             @Override
886             public boolean isEagerLoad()
887             {
888                 return sd.isEagerLoad();
889             }
890 
891             @Override
892             public String toString()
893             {
894                 return sd.toString();
895             }
896             
897             @Override
898             public int hashCode()
899             {
900                 final int prime = 31;
901                 int result = 1;
902                 result = prime * result + ((getServiceId() == null) ? 0 : getServiceId().hashCode());
903                 return result;
904             }
905 
906             @Override
907             public boolean equals(Object obj)
908             {
909                 if (this == obj) { return true; }
910                 if (obj == null) { return false; }
911                 if (!(obj instanceof ServiceDefImpl)) { return false; }
912                 ServiceDef other = (ServiceDef) obj;
913                 if (getServiceId() == null)
914                 {
915                     if (other.getServiceId() != null) { return false; }
916                 }
917                 else if (!getServiceId().equals(other.getServiceId())) { return false; }
918                 return true;
919             }
920 
921         };
922     }
923 
924     public static ModuleDef2 toModuleDef2(final ModuleDef md)
925     {
926         if (md instanceof ModuleDef2)
927             return (ModuleDef2) md;
928 
929         return new ModuleDef2()
930         {
931             @Override
932             public Set<AdvisorDef> getAdvisorDefs()
933             {
934                 return Collections.emptySet();
935             }
936 
937             @Override
938             public Class getBuilderClass()
939             {
940                 return md.getBuilderClass();
941             }
942 
943             @Override
944             public Set<ContributionDef> getContributionDefs()
945             {
946                 return md.getContributionDefs();
947             }
948 
949             @Override
950             public Set<DecoratorDef> getDecoratorDefs()
951             {
952                 return md.getDecoratorDefs();
953             }
954 
955             @Override
956             public String getLoggerName()
957             {
958                 return md.getLoggerName();
959             }
960 
961             @Override
962             public ServiceDef getServiceDef(String serviceId)
963             {
964                 return md.getServiceDef(serviceId);
965             }
966 
967             @Override
968             public Set<String> getServiceIds()
969             {
970                 return md.getServiceIds();
971             }
972 
973             @Override
974             public Set<StartupDef> getStartups()
975             {
976                 return Collections.emptySet();
977             }
978         };
979     }
980 
981     /**
982      * @since 5.1.0.2
983      */
984     public static ServiceLifecycle2 toServiceLifecycle2(final ServiceLifecycle lifecycle)
985     {
986         if (lifecycle instanceof ServiceLifecycle2)
987             return (ServiceLifecycle2) lifecycle;
988 
989         return new ServiceLifecycle2()
990         {
991             @Override
992             public boolean requiresProxy()
993             {
994                 return true;
995             }
996 
997             @Override
998             public Object createService(ServiceResources resources, ObjectCreator creator)
999             {
1000                 return lifecycle.createService(resources, creator);
1001             }
1002 
1003             @Override
1004             public boolean isSingleton()
1005             {
1006                 return lifecycle.isSingleton();
1007             }
1008         };
1009     }
1010 
1011     /**
1012      * @since 5.2.0
1013      */
1014     public static <T extends Comparable<T>> List<T> matchAndSort(Collection<? extends T> collection,
1015                                                                  Predicate<T> predicate)
1016     {
1017         assert predicate != null;
1018 
1019         List<T> result = CollectionFactory.newList();
1020 
1021         for (T object : collection)
1022         {
1023             if (predicate.accept(object))
1024                 result.add(object);
1025         }
1026 
1027         Collections.sort(result);
1028 
1029         return result;
1030     }
1031 
1032     /**
1033      * @since 5.2.0
1034      */
1035     public static ContributionDef2 toContributionDef2(final ContributionDef contribution)
1036     {
1037         if (contribution instanceof ContributionDef2)
1038             return (ContributionDef2) contribution;
1039 
1040         return new ContributionDef2()
1041         {
1042 
1043             @Override
1044             public Set<Class> getMarkers()
1045             {
1046                 return Collections.emptySet();
1047             }
1048 
1049             @Override
1050             public Class getServiceInterface()
1051             {
1052                 return null;
1053             }
1054 
1055             @Override
1056             public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources,
1057                                    Configuration configuration)
1058             {
1059                 contribution.contribute(moduleSource, resources, configuration);
1060             }
1061 
1062             @Override
1063             public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources,
1064                                    OrderedConfiguration configuration)
1065             {
1066                 contribution.contribute(moduleSource, resources, configuration);
1067             }
1068 
1069             @Override
1070             public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources,
1071                                    MappedConfiguration configuration)
1072             {
1073                 contribution.contribute(moduleSource, resources, configuration);
1074             }
1075 
1076             @Override
1077             public String getServiceId()
1078             {
1079                 return contribution.getServiceId();
1080             }
1081 
1082             @Override
1083             public String toString()
1084             {
1085                 return contribution.toString();
1086             }
1087         };
1088     }
1089 
1090     public static ContributionDef3 toContributionDef3(ContributionDef contribution)
1091     {
1092 
1093         if (contribution instanceof ContributionDef2)
1094         {
1095             return (ContributionDef3) contribution;
1096         }
1097 
1098         final ContributionDef2 cd2 = toContributionDef2(contribution);
1099 
1100         return new ContributionDef3()
1101         {
1102             @Override
1103             public boolean isOptional()
1104             {
1105                 return false;
1106             }
1107 
1108             @Override
1109             public String getServiceId()
1110             {
1111                 return cd2.getServiceId();
1112             }
1113 
1114             @Override
1115             public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, Configuration configuration)
1116             {
1117                 cd2.contribute(moduleSource, resources, configuration);
1118             }
1119 
1120             @Override
1121             public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, OrderedConfiguration configuration)
1122             {
1123                 cd2.contribute(moduleSource, resources, configuration);
1124             }
1125 
1126             @Override
1127             public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, MappedConfiguration configuration)
1128             {
1129                 cd2.contribute(moduleSource, resources, configuration);
1130             }
1131 
1132             @Override
1133             public Set<Class> getMarkers()
1134             {
1135                 return cd2.getMarkers();
1136             }
1137 
1138             @Override
1139             public Class getServiceInterface()
1140             {
1141                 return cd2.getServiceInterface();
1142             }
1143 
1144             @Override
1145             public String toString()
1146             {
1147                 return cd2.toString();
1148             }
1149         };
1150     }
1151 
1152     /**
1153      * @since 5.2.2
1154      */
1155     public static AdvisorDef2 toAdvisorDef2(final AdvisorDef advisor)
1156     {
1157         if (advisor instanceof AdvisorDef2)
1158             return (AdvisorDef2) advisor;
1159 
1160         return new AdvisorDef2()
1161         {
1162 
1163             @Override
1164             public ServiceAdvisor createAdvisor(ModuleBuilderSource moduleSource, ServiceResources resources)
1165             {
1166                 return advisor.createAdvisor(moduleSource, resources);
1167             }
1168 
1169             @Override
1170             public String getAdvisorId()
1171             {
1172                 return advisor.getAdvisorId();
1173             }
1174 
1175             @Override
1176             public String[] getConstraints()
1177             {
1178                 return advisor.getConstraints();
1179             }
1180 
1181             @Override
1182             public boolean matches(ServiceDef serviceDef)
1183             {
1184                 return advisor.matches(serviceDef);
1185             }
1186 
1187             @Override
1188             public Set<Class> getMarkers()
1189             {
1190                 return Collections.emptySet();
1191             }
1192 
1193             @Override
1194             public Class getServiceInterface()
1195             {
1196                 return null;
1197             }
1198 
1199             @Override
1200             public String toString()
1201             {
1202                 return advisor.toString();
1203             }
1204         };
1205     }
1206 
1207     /**
1208      * @since 5.2.2
1209      */
1210     public static DecoratorDef2 toDecoratorDef2(final DecoratorDef decorator)
1211     {
1212         if (decorator instanceof DecoratorDef2)
1213             return (DecoratorDef2) decorator;
1214 
1215         return new DecoratorDef2()
1216         {
1217 
1218             @Override
1219             public ServiceDecorator createDecorator(ModuleBuilderSource moduleSource, ServiceResources resources)
1220             {
1221                 return decorator.createDecorator(moduleSource, resources);
1222             }
1223 
1224             @Override
1225             public String[] getConstraints()
1226             {
1227                 return decorator.getConstraints();
1228             }
1229 
1230             @Override
1231             public String getDecoratorId()
1232             {
1233                 return decorator.getDecoratorId();
1234             }
1235 
1236             @Override
1237             public boolean matches(ServiceDef serviceDef)
1238             {
1239                 return decorator.matches(serviceDef);
1240             }
1241 
1242             @Override
1243             public Set<Class> getMarkers()
1244             {
1245                 return Collections.emptySet();
1246             }
1247 
1248             @Override
1249             public Class getServiceInterface()
1250             {
1251                 return null;
1252             }
1253 
1254             @Override
1255             public String toString()
1256             {
1257                 return decorator.toString();
1258             }
1259         };
1260     }
1261 
1262     /**
1263      * Determines if the indicated class is stored as a locally accessible file
1264      * (and not, typically, as a file inside a JAR). This is related to automatic
1265      * reloading of services.
1266      *
1267      * @since 5.2.0
1268      */
1269     public static boolean isLocalFile(Class clazz)
1270     {
1271         String path = PlasticInternalUtils.toClassPath(clazz.getName());
1272 
1273         ClassLoader loader = clazz.getClassLoader();
1274 
1275         // System classes have no visible class loader, and are not local files.
1276 
1277         if (loader == null)
1278             return false;
1279 
1280         URL classFileURL = loader.getResource(path);
1281 
1282         return classFileURL != null && classFileURL.getProtocol().equals("file");
1283     }
1284 
1285     /**
1286      * Wraps a {@link Coercion} as a {@link Mapper}.
1287      *
1288      * @since 5.2.0
1289      */
1290     public static <S, T> Mapper<S, T> toMapper(final Coercion<S, T> coercion)
1291     {
1292         assert coercion != null;
1293 
1294         return new Mapper<S, T>()
1295         {
1296             @Override
1297             public T map(S value)
1298             {
1299                 return coercion.coerce(value);
1300             }
1301         };
1302     }
1303 
1304     private static final AtomicLong uuidGenerator = new AtomicLong(System.nanoTime());
1305 
1306     /**
1307      * Generates a unique value for the current execution of the application. This initial UUID value
1308      * is not easily predictable; subsequent UUIDs are allocated in ascending series.
1309      *
1310      * @since 5.2.0
1311      */
1312     public static long nextUUID()
1313     {
1314         return uuidGenerator.incrementAndGet();
1315     }
1316 
1317     /**
1318      * Extracts the service id from the passed annotated element. First the {@link ServiceId} annotation is checked.
1319      * If present, its value is returned. Otherwise {@link Named} annotation is checked. If present, its value is
1320      * returned.
1321      * If neither of the annotations is present, <code>null</code> value is returned
1322      *
1323      * @param annotated
1324      *         annotated element to get annotations from
1325      * @since 5.3
1326      */
1327     public static String getServiceId(AnnotatedElement annotated)
1328     {
1329         ServiceId serviceIdAnnotation = annotated.getAnnotation(ServiceId.class);
1330 
1331         if (serviceIdAnnotation != null)
1332         {
1333             return serviceIdAnnotation.value();
1334         }
1335 
1336         Named namedAnnotation = annotated.getAnnotation(Named.class);
1337 
1338         if (namedAnnotation != null)
1339         {
1340             String value = namedAnnotation.value();
1341 
1342             if (InternalCommonsUtils.isNonBlank(value))
1343             {
1344                 return value;
1345             }
1346         }
1347 
1348         return null;
1349     }
1350 
1351 
1352     public static AnnotationProvider toAnnotationProvider(final Method element)
1353     {
1354         return InternalCommonsUtils.toAnnotationProvider(element);
1355     }
1356 
1357     public static <T> ObjectCreator<T> createConstructorConstructionPlan(final OperationTracker tracker, final ObjectLocator locator,
1358                                                                          final InjectionResources resources,
1359                                                                          final Logger logger,
1360                                                                          final String description,
1361                                                                          final Constructor<T> constructor)
1362     {
1363         return tracker.invoke(String.format("Creating plan to instantiate %s via %s",
1364                 constructor.getDeclaringClass().getName(),
1365                 constructor), new Invokable<ObjectCreator<T>>()
1366         {
1367             @Override
1368             public ObjectCreator<T> invoke()
1369             {
1370                 validateConstructorForAutobuild(constructor);
1371 
1372                 ObjectCreator[] constructorParameters = calculateParameters(locator, resources, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), tracker);
1373 
1374                 Invokable<T> core = new ConstructorInvoker<T>(constructor, constructorParameters);
1375 
1376                 Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core);
1377 
1378                 ConstructionPlan<T> plan = new ConstructionPlan(tracker, description, wrapped);
1379 
1380                 extendPlanForInjectedFields(plan, tracker, locator, resources, constructor.getDeclaringClass());
1381 
1382                 extendPlanForPostInjectionMethods(plan, tracker, locator, resources, constructor.getDeclaringClass());
1383 
1384                 return plan;
1385             }
1386         });
1387     }
1388 
1389     private static <T> void extendPlanForInjectedFields(final ConstructionPlan<T> plan, OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, Class<T> instantiatedClass)
1390     {
1391         Class clazz = instantiatedClass;
1392 
1393         while (clazz != Object.class)
1394         {
1395             Field[] fields = clazz.getDeclaredFields();
1396 
1397             for (final Field f : fields)
1398             {
1399                 // Ignore all static and final fields.
1400 
1401                 int fieldModifiers = f.getModifiers();
1402 
1403                 if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers))
1404                     continue;
1405 
1406                 final AnnotationProvider ap = new AnnotationProvider()
1407                 {
1408                     @Override
1409                     public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
1410                     {
1411                         return f.getAnnotation(annotationClass);
1412                     }
1413                 };
1414 
1415                 String description = String.format("Calculating possible injection value for field %s.%s (%s)",
1416                         clazz.getName(), f.getName(),
1417                         PlasticUtils.toTypeName(f.getType()));
1418 
1419                 tracker.run(description, new Runnable()
1420                 {
1421                     @Override
1422                     public void run()
1423                     {
1424                         final Class<?> fieldType = f.getType();
1425 
1426                         InjectService is = ap.getAnnotation(InjectService.class);
1427                         if (is != null)
1428                         {
1429                             addInjectPlan(plan, f, locator.getService(is.value(), fieldType));
1430                             return;
1431                         }
1432 
1433                         if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null)
1434                         {
1435                             Object value = resources.findResource(fieldType, f.getGenericType());
1436 
1437                             if (value != null)
1438                             {
1439                                 addInjectPlan(plan, f, value);
1440                                 return;
1441                             }
1442 
1443                             addInjectPlan(plan, f, locator.getObject(fieldType, ap));
1444                             return;
1445                         }
1446 
1447                         if (ap.getAnnotation(javax.inject.Inject.class) != null)
1448                         {
1449                             Named named = ap.getAnnotation(Named.class);
1450 
1451                             if (named == null)
1452                             {
1453                                 addInjectPlan(plan, f, locator.getObject(fieldType, ap));
1454                             } else
1455                             {
1456                                 addInjectPlan(plan, f, locator.getService(named.value(), fieldType));
1457                             }
1458 
1459                             return;
1460                         }
1461 
1462                         // Ignore fields that do not have the necessary annotation.
1463 
1464                     }
1465                 });
1466             }
1467 
1468             clazz = clazz.getSuperclass();
1469         }
1470     }
1471 
1472     private static <T> void addInjectPlan(ConstructionPlan<T> plan, final Field field, final Object injectedValue)
1473     {
1474         plan.add(new InitializationPlan<T>()
1475         {
1476             @Override
1477             public String getDescription()
1478             {
1479                 return String.format("Injecting %s into field %s of class %s.",
1480                         injectedValue,
1481                         field.getName(),
1482                         field.getDeclaringClass().getName());
1483             }
1484 
1485             @Override
1486             public void initialize(T instance)
1487             {
1488                 inject(instance, field, injectedValue);
1489             }
1490         });
1491     }
1492 
1493     private static boolean hasAnnotation(AccessibleObject member, Class<? extends Annotation> annotationType)
1494     {
1495         return member.getAnnotation(annotationType) != null;
1496     }
1497 
1498     private static <T> void extendPlanForPostInjectionMethods(ConstructionPlan<T> plan, OperationTracker tracker, ObjectLocator locator, InjectionResources resources, Class<T> instantiatedClass)
1499     {
1500         for (Method m : instantiatedClass.getMethods())
1501         {
1502             if (hasAnnotation(m, PostInjection.class) || hasAnnotation(m, PostConstruct.class))
1503             {
1504                 extendPlanForPostInjectionMethod(plan, tracker, locator, resources, m);
1505             }
1506         }
1507     }
1508 
1509     private static void extendPlanForPostInjectionMethod(final ConstructionPlan<?> plan, final OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, final Method method)
1510     {
1511         tracker.run("Computing parameters for post-injection method " + method,
1512                 new Runnable()
1513                 {
1514                     @Override
1515                     public void run()
1516                     {
1517                         final ObjectCreator[] parameters = calculateParametersForMethod(method, locator,
1518                                 resources, tracker);
1519 
1520                         plan.add(new InitializationPlan<Object>()
1521                         {
1522                             @Override
1523                             public String getDescription()
1524                             {
1525                                 return "Invoking " + method;
1526                             }
1527 
1528                             @Override
1529                             public void initialize(Object instance)
1530                             {
1531                                 Throwable fail = null;
1532 
1533                                 Object[] realized = realizeObjects(parameters);
1534 
1535                                 try
1536                                 {
1537                                     method.invoke(instance, realized);
1538                                 } catch (InvocationTargetException ex)
1539                                 {
1540                                     fail = ex.getTargetException();
1541                                 } catch (Exception ex)
1542                                 {
1543                                     fail = ex;
1544                                 }
1545 
1546                                 if (fail != null)
1547                                 {
1548                                     throw new RuntimeException(String
1549                                             .format("Exception invoking method %s: %s", method, ExceptionUtils.toMessage(fail)), fail);
1550                                 }
1551                             }
1552                         });
1553                     }
1554                 });
1555     }
1556 
1557 
1558     public static <T> ObjectCreator<T> createMethodInvocationPlan(final OperationTracker tracker, final ObjectLocator locator,
1559                                                                   final InjectionResources resources,
1560                                                                   final Logger logger,
1561                                                                   final String description,
1562                                                                   final Object instance,
1563                                                                   final Method method)
1564     {
1565 
1566         return tracker.invoke("Creating plan to invoke " + method, new Invokable<ObjectCreator<T>>()
1567         {
1568             @Override
1569             public ObjectCreator<T> invoke()
1570             {
1571                 ObjectCreator[] methodParameters = calculateParametersForMethod(method, locator, resources, tracker);
1572 
1573                 Invokable<T> core = new MethodInvoker<T>(instance, method, methodParameters);
1574 
1575                 Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core);
1576 
1577                 return new ConstructionPlan(tracker, description, wrapped);
1578             }
1579         });
1580     }
1581 
1582     /**
1583      * @since 5.3.1, 5.4
1584      */
1585     public final static Mapper<ObjectCreator, Object> CREATE_OBJECT = new Mapper<ObjectCreator, Object>()
1586     {
1587         @Override
1588         public Object map(ObjectCreator element)
1589         {
1590             return element.createObject();
1591         }
1592     };
1593 
1594     /**
1595      * @since 5.3.1, 5.4
1596      */
1597     public static Object[] realizeObjects(ObjectCreator[] creators)
1598     {
1599         return F.flow(creators).map(CREATE_OBJECT).toArray(Object.class);
1600     }
1601 
1602     /**
1603      * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings.
1604      *
1605      * @param map
1606      *         the map to extract keys from (may be null)
1607      * @return the sorted keys, or the empty set if map is null
1608      */
1609     
1610     public static List<String> sortedKeys(Map map)
1611     {
1612         return InternalCommonsUtils.sortedKeys(map);
1613     }
1614     
1615     /**
1616      * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
1617      * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
1618      * following word), thus "user_id" also becomes "User Id".
1619      */
1620     public static String toUserPresentable(String id)
1621     {
1622         return InternalCommonsUtils.toUserPresentable(id);
1623     }
1624 
1625     /**
1626      * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
1627      * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
1628      * underscore).
1629      *
1630      * @param expression a property expression
1631      * @return the expression with punctuation removed
1632      */
1633     public static String extractIdFromPropertyExpression(String expression)
1634     {
1635         return InternalCommonsUtils.extractIdFromPropertyExpression(expression);
1636     }
1637     
1638     /**
1639      * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
1640      * user presentable form.
1641      */
1642     public static String defaultLabel(String id, Messages messages, String propertyExpression)
1643     {
1644         return InternalCommonsUtils.defaultLabel(id, messages, propertyExpression);
1645     }
1646 
1647     public static String replace(String input, Pattern pattern, String replacement)
1648     {
1649         return InternalCommonsUtils.replace(input, pattern, replacement);
1650     }
1651 
1652 }